package com.arialyy.aria.m3u8.merge;


import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
import com.bskj.m3u8library.VideoProcessManager;
import com.bskj.m3u8library.VideoProcessor;
import com.bskj.m3u8library.listener.IVideoTransformListener;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


public class TSMergeManager {
    //demo use
    private HashMap<String , M3U8Seg> finishTSMap;
    private int tsNeedMergeCount;
    private int mergeCount;
    protected final Object mFinishMapLock = new Object();
    protected final Object mTimeLocke = new Object();
    protected final Object mFmp4FileLock = new Object();
    private static final String TSMergeTAG = "Aria::TSMerge";
    private int mTotalTs;
    private final M3U8 mM3U8;
    private final String mSaveDir;
    private final List<M3U8Seg> mTsList;
    private int currentTSCount = 0;
    private HashMap<String, List<Integer>> appendMap;
    ExecutorService singleAppendThreadExecutor;
    ExecutorService singleMergeThreadExecutor;
    //小段合成过后的TS
    private List<MergeTS> mergeTSList;
    private List<Integer> hasTsList;
    private String mergedWholeName = "merged.mp4";
    private String mergeName = "merged";
    static private String decryptFilePre = "decrypt_";
    private boolean isDownloadComplete = false;
    private int completeAppendCount = 0;
    private final int completeAppendMaxCount = 50;
    private TSMergeCallback mCallback;
    //兼容其他格式
    private TSMergeType type = TSMergeType.TS;
    private final String mergeTSStr = ".ts";
    private final String mergeAACStr = ".aac";
    private final String mergeMP4Str = ".mp4";
    private String mergeFileTypeStr = mergeTSStr;
    //是否使用转化
    private boolean needTransformToMP4 = false;
    //是否已经合成
    private boolean isMergeComplete = false;

    public TSMergeManager(M3U8 m3u8 ,String saveDir,String targetName,TSMergeCallback callback,TSMergeType type,Boolean needTransformToMP4){
        mCallback = callback;
        if (targetName != null && targetName.length() > 0) {
            mergeName = targetName;
        }
        this.type = type;
        switch (type) {
            case TS:
            case FMP4:
                mergeFileTypeStr = mergeMP4Str;
                break;
            case AAC:
                mergeFileTypeStr = mergeAACStr;
                break;
            case OTHER:
                break;
        }
        this.needTransformToMP4 = needTransformToMP4;
        mergedWholeName = mergeName + mergeFileTypeStr;
        mM3U8 = m3u8;
        mSaveDir = saveDir;
        mTsList = mM3U8.getTsList();
        mTotalTs = mTsList.size();
        singleAppendThreadExecutor = Executors.newSingleThreadExecutor();
        singleMergeThreadExecutor = Executors.newSingleThreadExecutor();
        initFinishedList();
        initDownloadTSList();
    }
    public void pauseMerge() {
        LogUtils.i(TSMergeTAG,"pauseMerge");
        singleAppendThreadExecutor.shutdown();
        singleMergeThreadExecutor.shutdown();
    }
    public String getMergedWholeName() {
        return mergedWholeName;
    }
    public void setCallback(TSMergeCallback callback) {
        this.mCallback = callback;
    }

    public void setNeedTransformToMP4(boolean needTransformToMP4) {
        this.needTransformToMP4 = needTransformToMP4;
    }
    public void resetMergeThread() {
        LogUtils.i(TSMergeTAG,"resetMergeThread");
        if (singleAppendThreadExecutor.isTerminated()) {
            singleAppendThreadExecutor = Executors.newSingleThreadExecutor();
        }
        if (singleMergeThreadExecutor.isTerminated()) {
            singleMergeThreadExecutor = Executors.newSingleThreadExecutor();
        }


    }
    public static List<Integer>  getDownloadTSIndexList(String saveDir,int total) {
        List<Integer> result = new ArrayList<Integer>();
        File tempTsFile = new File(saveDir);
        if (!tempTsFile.exists()) {
            return result;
        }
        for (File sub : tempTsFile.listFiles())
        {
            String name = sub.getName();
            if (!name.endsWith(".ts")) {
                continue;
            }
            if (name.contains(".mp4")) {
                for (int i = 0; i < total ; i ++) {
                    result.add(i);
                }
                break;
            }
            if (sub.length() == 0) {
                sub.delete();
                continue;
            }
            String[] list = name.split(".ts");
            if (list.length > 0) {
                String namePre = list[0];
                if (namePre.startsWith(decryptFilePre)) {
                    namePre.replace(decryptFilePre,"");
                }
                String[] indexList = namePre.split("_");
                if (indexList.length == 2) {
                    String firstStr = indexList[0];
                    if (firstStr.contains("video")) {
                        int end = Integer.parseInt(indexList[1]);
                        if (!result.contains(end) && end <= total) {
                            result.add(end);
                        }
                    }else {
                        int first = Integer.parseInt(indexList[0]);
                        int end = Integer.parseInt(indexList[1]);
                        for (int i = first; i <= end; i++) {
                            if (!result.contains(i)) {

                                if (!result.contains(i) && i <= total) {
                                    result.add(i);
                                }
                            }
                        }
                    }

                }
            }
        }
        return result;
    }
    public void initDownloadTSList() {
        hasTsList = new ArrayList<Integer>();
        File tempTsFile = new File(mSaveDir);
        if (!tempTsFile.exists()) {
            return ;
        }
        for (File sub : tempTsFile.listFiles())
        {
            String name = sub.getName();
            if (!name.endsWith(".ts") && !name.endsWith(".mp4") && !name.endsWith(".aac")) {
                continue;
            }
            if (name.contains(mergedWholeName) || checkMergeLastTSFile()) {
                for (int i = 0; i < mTotalTs ; i ++) {
                    hasTsList.add(i);
                }
                break;
            }
            if (sub.length() == 0) {
                sub.delete();
                continue;
            }
            String[] list = name.split(".ts");
            if (list.length > 0) {
                String namePre = list[0];
                if (namePre.startsWith(decryptFilePre)) {
                    namePre.replace(decryptFilePre,"");
                }
                String[] indexList = namePre.split("_");
                if (indexList.length == 2) {
                    String firstStr = indexList[0];
                    if (firstStr.contains("video")) {
                        int end = Integer.parseInt(indexList[1]);
                        if (!hasTsList.contains(end)) {
                            hasTsList.add(end);
                        }

                        if (finishTSMap.get(String.valueOf(end)) == null && mTsList.size() > end) {
                            M3U8Seg ts = mTsList.get(end);
                            ts.setDownloadStatus(1);
                            String indexStr = String.valueOf(end);
                            if (indexStr != null && ts != null) {
                                finishTSMap.put(String.valueOf(end),ts);
                            }
                        }
                    }else {
                        int first = Integer.parseInt(indexList[0]);
                        int end = Integer.parseInt(indexList[1]);
                        for (int i = first; i <= end; i++) {
                            if (!hasTsList.contains(i)) {
                                if (i < mTotalTs) {
                                    M3U8Seg ts = mTsList.get(i);
                                    ts.setMerge(1);
                                    finishTSMap.put(String.valueOf(end),ts);
                                }

                                if (!hasTsList.contains(i)) {
                                    hasTsList.add(i);
                                }
                            }
                        }
                        MergeTS ts = new MergeTS(name,sub.getPath(),first,end,first,true);
                        addMergeTS(ts);
                    }

                }
            }
        }
        currentTSCount = hasTsList.size();
        if (currentTSCount >= mTotalTs) {
            currentTSCount = mTotalTs;
//            if (checkMergeLastTSFile()) {
//                completeMergeToMp4();
//            }
//            if (checkMergeLastMp4Exists()) {
//                mCallback.tsMergeComplete(true);
//            }
        }

        checkTsNeedMerge();
        startAppendLargeTS();
    }

    private void doMergeTs() {
        LogUtils.i(TSMergeTAG, "doMergeTs --->  ");
        String mSaveName = VideoDownloadUtils.computeMD5(mM3U8.getUrl());
        File localM3U8File = new File(mSaveDir, mSaveName + "_" + VideoDownloadUtils.LOCAL_M3U8);
        String inputPath = localM3U8File.getAbsolutePath();

        File saveDir = new File(mSaveDir);
        File outputFile = new File(saveDir.getParent(), mergedWholeName);
        String outputPath = outputFile.getAbsolutePath();
        LogUtils.i(TSMergeTAG, "doMergeTs ---> inputPath : " + inputPath
                + ", outputPath : " + outputPath
                + ", mergedWholeName : " + mergedWholeName);

        VideoProcessManager.getInstance().transformM3U8ToMp4(inputPath, outputPath, new IVideoTransformListener() {
            @Override
            public void onTransformProgress(float progress) {
                LogUtils.i(TSMergeTAG, "doMergeTs ---> onTransformProgress progress = " + progress);
            }

            @Override
            public void onTransformFailed(Exception e) {
                LogUtils.i(TSMergeTAG, "doMergeTs ---> onTransformFailed err = " + e.getMessage());
                mCallback.tsMergeComplete(false);
            }

            @Override
            public void onTransformFinished() {
                LogUtils.i(TSMergeTAG, "doMergeTs ---> onTransformFinished outputPath=" + outputPath);

                FileUtil.deleteDir(new File(mSaveDir));
                mCallback.tsMergeComplete(true);
            }
        });
    }

    public void downloadCompleteMerge(){
        if (mM3U8.isMergeByLocalM3u8()) {
            try {
                NewM3U8Utils.createLocalM3U8File(mM3U8, mSaveDir);
                NewM3U8Utils.createLocalM3U8FileWithKey(mM3U8, mSaveDir);
            } catch (Exception e) {
                e.printStackTrace();
            }

            doMergeTs();
            return;
        }

        LogUtils.e(TSMergeTAG,"download complete!");
        isDownloadComplete = true;
        if (checkMergeLastMp4Exists()) {
//            clearCacheDir();
            mCallback.tsMergeComplete(true);
            return;
        }
        if (checkMergeLastTSFile()) {
            singleAppendThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    completeMergeToMp4();
                }
            });
        }else {
            LogUtils.e(TSMergeTAG,"download complete to merge last ts!");
            startMergeListTS();
            LogUtils.e(TSMergeTAG,"download complete to appended last ts!");
            startAppendLargeTS();

        }
    }
    private boolean checkMergeLastTSFile() {
        String path = getMergeCompleteTSFile();
        File file = new File(path);
        return file.exists();

    }
    private String getMergeCompleteTSFile() {
        String path = "";
        if (mTotalTs == 1) {
            path = mSaveDir + File.separator + VideoDownloadUtils.SEGMENT_PREFIX + "0.ts";
        }else {
            path = mSaveDir + File.separator + "0_" + String.valueOf(mTotalTs - 1) + ".ts";
        }
        return path;
    }
    private  boolean checkMergeLastMp4Exists() {
        String path = mSaveDir + File.separator + mergedWholeName;
        File file = new File(path);
        return file.exists();
    }
    public List<Integer> getHasTsList() {
        return hasTsList;
    }

    private void getMergeTSListWithDir() {
        File tempTsFile = new File(mSaveDir);
        for (File sub : tempTsFile.listFiles())
        {
            String name = sub.getName();
            String[] list = name.split(".ts");
            if (list.length > 0) {
                String namePre = list[0];
                String[] indexList = namePre.split("_");
                if (indexList.length == 2) {
                    int first = Integer.parseInt(indexList[0]);
                    int end = Integer.parseInt(indexList[1]);
                    MergeTS ts = new MergeTS(name,sub.getPath(), first, end, first,true);
                    addMergeTS(ts);
                }
            }
        }
    }

    //demo use
    private void initFinishedList() {
        tsNeedMergeCount = 5;
        mergeCount = 0;
        finishTSMap = new HashMap<String,M3U8Seg>();
        appendMap = new HashMap<String,List<Integer>>();
        mergeTSList = new ArrayList<MergeTS>();
        for (int i = 0; i < mTotalTs ; i++) {
            M3U8Seg ts = mTsList.get(i);
            if (ts.getDownloadStatus() != 0) {
                finishTSMap.put(String.valueOf(i),ts);
            }
        }
    }
    //add download result ts array
    public void addFinishedTS(M3U8Seg ts,int status) {
        if (ts == null) {
            LogUtils.e(TSMergeTAG,"addFinishedTS error has null");
            return;
        }
//        if (currentTSCount > mTotalTs) {
//
//            return;
//        }
        if (finishTSMap.get(String.valueOf(ts.getIndex())) != null) {
            LogUtils.e(TSMergeTAG,"addFinishedTS error has this ts:"+ts.getIndex());
            return;
        }
        ts.setDownloadStatus(status);
        if (status == 1 || status == 2) {
            synchronized (mFinishMapLock) {
                LogUtils.i(TSMergeTAG,"add ts to map:" + ts.getIndex());
                //强制更新文件名
                ts.setName(VideoDownloadUtils.SEGMENT_PREFIX + ts.getIndex() + ".ts");
                finishTSMap.put(String.valueOf(ts.getIndex()),ts);
                currentTSCount ++;
            }
        }else {
            LogUtils.i(TSMergeTAG,"add ts to map failed:" + ts.getIndex());
        }
        startMergeListTS();
    }
    private void startMergeListTS() {
        if (mM3U8.isMergeByLocalM3u8()) {
            return;
        }
        LogUtils.i(TSMergeTAG,"ts merge ts list");
        if (checkMergeLastTSFile()) {
            LogUtils.i(TSMergeTAG,"ts merge success");
            return;
        }
        if (singleMergeThreadExecutor.isTerminated() || singleMergeThreadExecutor == null) {
            LogUtils.e(TSMergeTAG,"singleMergeThreadExecutor stop!");

        }
//        mMergeThreadPool.execute(new Runnable() {
        try {
            singleMergeThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        LogUtils.i(TSMergeTAG,"start check merge ts!");
                        checkTsNeedMerge();
                    }catch (Exception e) {
                        LogUtils.e(TSMergeTAG,"merge list error"+e.getMessage());
                    }
                }
            });
        }catch (Exception e) {
            LogUtils.e(TSMergeTAG,"singleMergeThreadExecutor execute error:" + e.getMessage());
        }

    }
    private  void checkTsNeedMerge() {
//        if (mergeCount >= mTotalTs) {
//            return;
//        }
        try {
            LogUtils.i(TSMergeTAG,"check ts to merge");
            if (mTotalTs <= tsNeedMergeCount) {
                if (mTotalTs == finishTSMap.size()) {
                    List<M3U8Seg> list = getNeedMergeTS(0);
//                synchronized (mFmp4FileLock) {
                    doMergeTS(0, list);
//                }
                }
            }else {
                for (int i = 0; i < mTotalTs ; i = i + tsNeedMergeCount) {
                    int index = hasConsecutive(i);
                    if (index >= 0) {
                        List<M3U8Seg> list = getNeedMergeTS(index);
//                    synchronized (mFmp4FileLock) {
                        doMergeTS(index, list);
//                    }
                    }
                }
            }
        }catch (Exception e) {
            LogUtils.e(TSMergeTAG,e.getMessage());
        }

    }
    private List<M3U8Seg> getNeedMergeTS(int index) {
        List<M3U8Seg> list = new ArrayList<>();
        for (int i = index ; (i < index + tsNeedMergeCount && i < mTotalTs) ; i ++) {
            M3U8Seg ts = finishTSMap.get(String.valueOf(i));

            if (ts != null && ts.getDownloadStatus() != 0 && ts.getMerge() == 0) {
                list.add(ts);
            }
        }
        return list;
    }
    private M3U8Seg getFirstCanAppendM3U8Seg(List<M3U8Seg> list) {
        for (M3U8Seg ts : list) {
            if (ts.getMerge() != 2 && ts.getDownloadStatus() == 1) {
                return ts;
            }
        }
        return null;
    }
    private MergeTS getFirstCanAppendMergeTS(List<MergeTS> list) {
        for (MergeTS ts : list) {
            if (ts.isCanAppend() || ts.isMergeStatus()) {
                return ts;
            }
        }
        return null;
    }
    private void doMergeTS(int index, List<M3U8Seg> list) {
            try {
                M3U8Seg result = doMergeTSList(index,list);
                MergeTS ts;
                int first = index;
                int last = index + tsNeedMergeCount - 1;
                if (last >= mTotalTs) {
                    last = mTotalTs - 1;
                }
                if (result != null) {
                    ts = new MergeTS(result.getName(), getTSPath(result), first,last, (index / tsNeedMergeCount), true);
                    LogUtils.w(TSMergeTAG,"merge TS List:"+String.valueOf(first) + "-" + String.valueOf(last) + "status : true");
                    if (needTransformToMP4 == false) {
                        ALog.i(TSMergeTAG,"decryptTS ts:"+ts.getPath());
                        decryptTS(ts);
                    }
                    deleteTSList(list,result);

                }else {
                    ts = new MergeTS(result.getName(), getTSPath(result), first,last, (index / tsNeedMergeCount), false);
                    LogUtils.w(TSMergeTAG,"merge TS List:"+String.valueOf(first) + "-" + String.valueOf(last) + "status : false");
//                    deleteTSList(list,null);
                }

                addTSList(ts);
            }catch (Exception e) {
                LogUtils.e(TSMergeTAG,e.getMessage());
            }
    }
    private boolean hasDecryptKey() {
        boolean ret = false;
        M3U8Seg segTS = mTsList.get(0);
        File keyFile = new File(mSaveDir, segTS.getLocalKeyUri());
        if (segTS.hasKey() && keyFile.exists()) {
            ret = true;
        }
        return ret;
    }
    private boolean decryptTS(MergeTS ts) {
        try {
            if (needTransformToMP4 || !hasDecryptKey()) {
                return false;
            }
            String tsPath = ts.getPath();
            File file = new File(tsPath);
            String inputPath = createLocalM3U8FileForDecrypt(file,ts.getFirstIndex());
            VideoProcessor processor = new VideoProcessor();
            String outputPath = mSaveDir + File.separator + decryptFilePre + ts.getFirstIndex() +"_"+ts.getLastIndex() + ".ts";
            int ret = processor.decryptTSFile(inputPath,outputPath);
            File outputFile = new File(outputPath);
            if (ret == 0) {
                ALog.i(TSMergeTAG,"decryptTS success:"+ts.getPath());
                if (outputFile.exists()) {
                    file.delete();
                    ts.setPath(outputPath);
//                    outputFile.renameTo(file);
                    ALog.i(TSMergeTAG,"decryptTS success rename to old path:"+ts.getPath());
                }
            }else {
                file.delete();
                ts.setMergeStatus(false);
                ALog.i(TSMergeTAG,"decryptTS failed:"+ts.getPath());
            }
            //
            File m3u8File = new File(inputPath);

            if (m3u8File.exists()) {
                m3u8File.delete();
            }
            return ret == 0;
//            if (outputFile.exists()) {
//                outputFile.delete();
//            }
        }catch (Exception e) {
            ALog.e(TSMergeTAG,"decrypt ts error:" + e.toString());
        }
        return true;
    }
    //当fmp4作为容器时，有#EXT-MAP的header时，在第一个添加header
    private void doAppendHeader(M3U8Seg ts, VideoProcessor processor) {
        if (type != TSMergeType.FMP4) {
            return;
        }
        String headerPath = getMapHeaderPath(0);
        File file = new File(headerPath);
        if (!file.exists()) {
            return;
        }
        String tsPath = getTSPath(ts);
        int ret = processor.appendTS(headerPath,tsPath);
        if (ret == 0) {
            //拼接成功，删除ts文件，重命名header文件
            File tsFile = new File(tsPath);
            if (tsFile.delete()) {
                file.renameTo(tsFile);
                file.delete();
                LogUtils.i(TSMergeTAG,"append FMP4 Header success");
            }else {
                LogUtils.e(TSMergeTAG,"append FMP4 Header delete ts file error!\n path:" + tsPath);
            }
        }else {
            LogUtils.e(TSMergeTAG,"append FMP4 Header error status:" + ret);
        }

    }
    //get header 文件位置
    private String getMapHeaderPath(int index) {
        return mSaveDir + File.separator + "local_" + index + ".map";
    }
    private M3U8Seg doMergeTSList(int index, List<M3U8Seg> list){
        LogUtils.i(TSMergeTAG,"doMergeTSList function!");
        M3U8Seg firstTs = getFirstCanAppendM3U8Seg(list);
        if (firstTs == null) {
            LogUtils.i(TSMergeTAG,"doMergeTSList can not find firstTS!");
            return null;
        }
        VideoProcessor processor = new VideoProcessor();
        if (index == 0 ) {
            doAppendHeader(firstTs,processor);
        }
        int firstIndex = list.indexOf(firstTs);

        int i = firstIndex + 1;
        boolean allFailed = true;
        //依次追加
        for (; i < list.size();) {
            M3U8Seg ts = list.get(i);
            if (firstTs.equals(ts)) {
                i ++;
                continue;
            }
            if (ts.getMerge() == 0 && ts.getDownloadStatus() == 1) {
                allFailed = false;
                int tempResult = processor.appendTS(getTSPath(firstTs),getTSPath(ts));
                if (tempResult == 0) {
                    firstTs.setMerge(1);
                    ts.setMerge(1);
                    i ++;
                    LogUtils.i(TSMergeTAG,"doMergeTSList success:"+firstTs.getIndex() + "+"+ts.getIndex());
                }else if (tempResult == -2) {
                    //第一个文件打开失败，后重置第一个文件追加
                    firstTs.setMerge(2);
                    firstTs = getFirstCanAppendM3U8Seg(list);
                    if (firstTs == null) {
                        break;
                    }else {
                        i = list.indexOf(firstTs) + 1;
                    }
                    LogUtils.e(TSMergeTAG,"doMergeTSList failed:"+firstTs.getIndex() + "+"+ts.getIndex());
                }else if (tempResult == -3) {
                    //第二个文件打开失败，继续追回之后的文件
                    ts.setMerge(2);
                    i ++;
                    LogUtils.e(TSMergeTAG,"doMergeTSList failed:"+firstTs.getIndex() + "+"+ts.getIndex());
                }else {
                    //失败，后重置第一个文件追加
                    firstTs.setMerge(2);
                    ts.setMerge(2);
                    firstTs = getFirstCanAppendM3U8Seg(list);
                    if (firstTs == null) {
                        break;
                    }else {
                        i = list.indexOf(firstTs) + 1;
                    }
                    LogUtils.e(TSMergeTAG,"doMergeTSList failed:"+firstTs.getIndex() + "+"+ts.getIndex());
                }
            }else {
                i ++;
            }
        }
        if (allFailed == true) {
            for (M3U8Seg ts:list) {
                ts.setMerge(2);
            }
        }
//        renameFile(getTSPath(firstTs),);
        int first = index;
        int last = index + tsNeedMergeCount - 1;
        if (last >= mTotalTs) {
            last = mTotalTs - 1;
        }
        String path = getTSPath(firstTs);
        if (first == last) {
            return firstTs;
        }
        String name = renameMergeTS(path,first,last,true);
        firstTs.setName(name);
        return firstTs;
    }
    private String renameMergeTS(String path,int firstIndex,int lastIndex,Boolean returnName) {
        int nameIndex = path.lastIndexOf("/");
        String name = path.substring(nameIndex);
        String newName = String.format("%d_%d.ts",firstIndex,lastIndex);
//        String newName = String.format("/%d_%d.ts",firstIndex,lastIndex);
        String newPath = path.replace(name,"/"+newName);
        File newFile = new File(newPath);
        File file = new File(path);

        if (newFile.exists()) {
            newFile.delete();
        }
        VideoProcessor processor = new VideoProcessor();
        if (!processor.renameTSToMP4(path,newPath)) {
            LogUtils.e(TSMergeTAG,"renameError path:"+path+"\n new path:" + newPath);
        }else {
            if (file.exists()) {
                file.delete();
            }
        }

//        renameFile(path,newPath);
        if (returnName) {
            return newName;
        }else {
            return newPath;
        }

    }
    private void startAppendLargeTS() {
        if (mM3U8.isMergeByLocalM3u8()) {
            return;
        }
        LogUtils.i(TSMergeTAG,"start append largeTS!");
//        mAppendThreadPool.execute(new Runnable() {
        if (singleAppendThreadExecutor.isTerminated()) {
            LogUtils.e(TSMergeTAG,"singleAppendThreadExecutor stop!");
        }
        try {
            singleAppendThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        appendTSAction();
                        completeMergeToMp4();
                    }catch (Exception e) {
                        LogUtils.e(TSMergeTAG,"startAppendLargeTS:" + e.getMessage());
                    }

                }
            });
        }catch (Exception e) {
            LogUtils.e(TSMergeTAG,"singleAppendThreadExecutor error:"+e.getMessage());
        }

    }
    private void addTSList(MergeTS ts) {
        synchronized (mFmp4FileLock) {
            if (!addMergeTS(ts)) {
                return;
            }
        }
        if (ts.isCanAppend() && ts.isMergeStatus()) {
            startAppendLargeTS();
        }
    }
    private boolean addMergeTS(MergeTS ts) {
        File file = new File(ts.getPath());
        if (!file.exists()) {
            return false;
        }
        for (MergeTS inTs : mergeTSList) {
            if (inTs.getPath().equals(ts.getPath())) {
                return false;
            }
        }
        synchronized (mFmp4FileLock){
            mergeTSList.add(ts);
        }
        return true;
    }
    private boolean removeTSFormList(MergeTS ts) {
        boolean result = false;
        synchronized (mFmp4FileLock) {
            result = mergeTSList.remove(ts);
        }
        if (result) {
            return true;
        }
        MergeTS needDeleteFmp4 = null;
        for (MergeTS temp : mergeTSList) {
            if (temp.getPath() == ts.getPath()) {
                needDeleteFmp4 = temp;
                break;
            }
        }
        if (needDeleteFmp4 != null) {
            synchronized (mFmp4FileLock) {
                result = mergeTSList.remove(needDeleteFmp4);
            }
        }
        if (!result) {
            LogUtils.e(TSMergeTAG,"remove failed:" + ts.getPath());
        }
        return result;
    }
    private void appendTSAction() {
        LogUtils.i(TSMergeTAG,"start append TS Action");
        if (checkMergeLastMp4Exists() || checkMergeLastTSFile()) {
            LogUtils.i(TSMergeTAG,"appendTSAction check complete file!");
            return;
        }
        synchronized (mFmp4FileLock) {
//            if (getEfficientTSCount() <= 1) {
//                return;
//            }
            MergeTS firstTS = null;
            MergeTS secondTS = null;

            for (MergeTS ts : mergeTSList) {
                File file = new File(ts.getPath());
                if (ts.isMergeStatus() && ts.isCanAppend() && !ts.isAppending() && file.exists() && file.length() > 0) {
                    firstTS = ts;
                }

                if (firstTS == null) {
                    LogUtils.e(TSMergeTAG, "appendTSAction no can append firts ts");
                    continue;
                }
                int firstFmp4First = firstTS.getFirstIndex();
                int firstFmp4Last = firstTS.getLastIndex();

                int targetFirst = firstFmp4Last + 1;
                int targetLast = firstFmp4First - 1;
                //找到相领的TS
                secondTS = getPreTS(targetLast);
                if (secondTS == null) {
                    secondTS = getBackTS(targetFirst);
                }
                if (secondTS == null) {
                    LogUtils.e(TSMergeTAG, "appendTSAction no match other ts:" + firstTS.getPath());
                    LogUtils.e(TSMergeTAG, "targetFirst:" + targetFirst + "targetLast:" + targetLast);
                    continue;
                }else {
                    break;
                }
            }
            appendMergeTS(firstTS, secondTS);
        }
    }
    private MergeTS getPreTS(int preIndex) {
        int i = 0;
        for (;i < mergeTSList.size();) {
            MergeTS ts = mergeTSList.get(i);
            int last = ts.getLastIndex();
            int first = ts.getFirstIndex();
            if (preIndex == last) {
                if (ts.isMergeStatus() && ts.isCanAppend()) {
                    LogUtils.e(TSMergeTAG,"getPreTS");
                    if (!ts.isAppending()) {
                        return ts;
                    } else {
                        return null;
                    }
                }else {
                    i = 0;
                    preIndex = first - 1;
                }
            }
            i++;
        }
        return null;
    }
    private MergeTS getBackTS(int backIndex) {
        int i = 0;
        for (;i < mergeTSList.size();) {
            MergeTS ts = mergeTSList.get(i);
            int last = ts.getLastIndex();
            int first = ts.getFirstIndex();
            if (backIndex == first) {
                if (ts.isMergeStatus() && ts.isCanAppend()) {
                    if (!ts.isAppending()) {
                        return ts;
                    } else {
                        return null;
                    }
                }else {
                    i = 0;
                    backIndex = last + 1;
                }
            }
            i++;
        }
        return null;
    }
    private void deleteFMP4File(MergeTS ts) {
        String path = ts.getPath();
        File file = new File(path);
        if (file.delete()) {
            LogUtils.i(TSMergeTAG,"delete fmp4 file:"+file.getPath());
        }else {
            LogUtils.i(TSMergeTAG,"delete fmp4 file failed:"+file.getPath());
        }
    }

    private void appendMergeTS(MergeTS first, MergeTS second) {
        LogUtils.i(TSMergeTAG,"appendMergeTS function");
        if (first == null || second == null) {
            return;
        }


        first.setAppending(true);
        second.setAppending(true);
        if (first.getFirstIndex() > second.getFirstIndex()) {
            MergeTS tempFmp4 = first;
            first = second;
            second = tempFmp4;
        }
        String firstPath = first.getPath();
        String secondPath = second.getPath();
        File firstFile = new File(firstPath);
        File secondFile = new File(secondPath);

        //查漏：有合成的list切片没有解密的
        if (needTransformToMP4 == false && hasDecryptKey() && first.getLastIndex() - first.getFirstIndex() < tsNeedMergeCount && !firstFile.getName().startsWith(decryptFilePre) ) {
            boolean ret = decryptTS(first);
            if (!ret) {
                first.setAppending(false);
                second.setAppending(false);
                appendTSAction();
                return;
            }

        }
        if (needTransformToMP4 == false && hasDecryptKey() && second.getLastIndex() - second.getFirstIndex() < tsNeedMergeCount && !secondFile.getName().startsWith(decryptFilePre) ) {
            boolean ret = decryptTS(second);
            if (!ret) {
                first.setAppending(false);
                second.setAppending(false);
                appendTSAction();
                return;
            }
        }

//        if (!firstFile.exists() || !secondFile.exists()){
//            first.setAppending(false);
//            second.setAppending(false);
//            return;
//        }


        VideoProcessor processor = new VideoProcessor();
        int result = processor.appendTS(firstPath,secondPath);
        if (result == 0) {
//            synchronized (mFinshiMapLock) {
//                removeTSFormList(first);
                removeTSFormList(second);
//            }


//            List<M3U8Seg> list = first.getTsList();
//            list.addAll(second.getTsList());
//            first.setTsList(list);
//            first.setFirstIndex(first.getFirstIndex());
            int firstIndex = first.getFirstIndex();
            int lastIndex = second.getLastIndex();
            String path = firstPath;
            String newPath = renameMergeTS(firstPath,firstIndex,lastIndex,false);
            first.setPath(newPath);
            first.setFirstIndex(firstIndex);
            first.setLastIndex(lastIndex);
            LogUtils.i(TSMergeTAG,"append Merge TS:"+firstPath+"\n"+secondPath+"\n"+newPath+"\n");

//            addMergeTS(first);
            int index = mergeTSList.indexOf(first);
            if (index == 0) {
                LogUtils.e(TSMergeTAG,"append Merge TS can not find firs ts:" + index);
            }else {
                LogUtils.i(TSMergeTAG,"append Merge fist TS path:"+first.getPath()+"\n"+"first  index"+first.getFirstIndex()+"\n"+"last index"+first.getLastIndex()+"\n");
            }
//            printMerTSList();
            deleteFMP4File(second);
        }else {
//            int count = first.getReMergeTime();
//            if (first.getReMergeTime() < 5) {
////                Thread.sleep(1000);
//                File dirFile = new File(mSaveDir);
//                boolean dirRet1 = dirFile.canRead();
//                boolean dirRet2 = dirFile.canWrite();
//                File file = new File(firstPath);
//
//                boolean ret1 = file.setReadable(true,true);
//                boolean ret2 = file.setWritable(true,true);
//                boolean read = file.canRead();
//                boolean write = file.canWrite();
//                count ++;
//                first.setReMergeTime(count);
//                appendMergeTS(first,second);
//                return;
//            }


            if (result == -2) {
                first.setCanAppend(false);
            }else if (result == -3) {
                second.setCanAppend(false);
            }else {
                first.setCanAppend(false);
                second.setCanAppend(false);
            }
//            appendTSAction();
//            startAppendLargeTS();
        }
        first.setAppending(false);
        second.setAppending(false);
        startAppendLargeTS();
    }
    private void printMerTSList() {
        for (MergeTS ts : mergeTSList) {
            LogUtils.i(TSMergeTAG,"Merge fist complete list TS path:"+ts.getPath()+"\n"+"first  index"+ts.getFirstIndex()+"\n"+"last index"+ts.getLastIndex()+"\n");
        }
    }
    private void clearCacheDir() {
        //清除缓存文件夹
        File dirFile = new File(mSaveDir);
        FileUtil.deleteDir(dirFile);

    }
    private void completeMergeToMp4() {
        try {
            if (isDownloadComplete == false) {
                isDownloadComplete = true;
            }
            if (isMergeComplete) {
                clearCacheDir();
                return;
            }
            String path = getMergeCompleteTSFile();
            File file = new File(path);
            if (file.exists()) {
                if (type == TSMergeType.TS) {
                    completeHandleWithTSFile(path);
                } else if (type == TSMergeType.AAC || type == TSMergeType.FMP4) {
                    completeHandleWithRenameFile(path);
                }
                pauseMerge();

            }
        }catch (Exception e) {
            mCallback.tsMergeComplete(false);
            LogUtils.e(TSMergeTAG,e.getMessage());
        }
    }
    private void completeHandleWithRenameFile(String lastPath) {
        File file = new File(lastPath);
        if (!file.exists() || file.length() == 0) {
            isMergeComplete = false;
            mCallback.tsMergeComplete(false);
            return;
        }
        String[] array = lastPath.split(file.getName());
        String newPath = array[0] + mergedWholeName;
        File newFile = new File(newPath);
        if (newFile.exists()) {
            newFile.delete();
        }
        VideoProcessor processor = new VideoProcessor();
        if (!processor.renameTSToMP4(lastPath,newPath)) {
            LogUtils.e(TSMergeTAG,"renameError");
            isMergeComplete = false;
            mCallback.tsMergeComplete(false);
            return;
        }

        if (file.exists()) {
            file.delete();
        }
        File dirFile = newFile.getParentFile();
        for (File subFile : dirFile.listFiles()) {
            if (!subFile.getPath().equals(newPath)) {
                subFile.delete();
            }
        }
        isMergeComplete = true;
        mCallback.tsMergeComplete(true);
    }
    private void completeHandleWithTSFile(String lastPath) throws Exception {
            File file = new File(lastPath);
            String inputPath = createLocalM3U8FileForFMP4(file);
            VideoProcessor processor = new VideoProcessor();
//                    if (!processor.isTSFile(ts.getPath())) {
//                        LogUtils.e(TSMergeTAG,"transform path  is not ts!" + path);
//                        return;
//                    }
            int result = 0;
            if (needTransformToMP4) {
                result = processor.transfromTSToMP4(inputPath, lastPath);
            }
            if (result != 0) {
                LogUtils.e(TSMergeTAG,"transform ts to mp4 error!");
                isMergeComplete = false;
                mCallback.tsMergeComplete(false);
            }else {
                completeHandleWithRenameFile(lastPath);
            }
    }
    private void renameFile(String path, String newPath) {
        File file = new File(path);
        if (path.isEmpty()) {
            return;
        }
        File newFile = new File(newPath);
        if (!file.renameTo(newFile)) {
            LogUtils.i(TSMergeTAG,"rename ts error:"+path);
        }else {
            file.delete();
        }
        if (!newFile.exists()) {
            LogUtils.e(TSMergeTAG,"now new file");
        }
    }
    private int getEfficientTSCount() {
        int count = 0;
        for (int i = 0; i < mergeTSList.size(); i++) {
            MergeTS ts = mergeTSList.get(i);
            if (ts.isMergeStatus() && ts.isCanAppend()) {
                count ++;
            }
        }
        return count;
    }

    private void  deleteTSList(List<M3U8Seg> list,M3U8Seg except) {
        if (list == null || list.size() == 0) {
            return;
        }
        for (M3U8Seg ts : list) {
            if (except != null && ts.getName().equals(except.getName())) {
                continue;
            }
            deleteTS(ts);
        }
    }
//    private void  deleteFMP4M3U8(int index) {
//        String path = mSaveFmp4Dir.getAbsolutePath() + File.separator + String.format("%d.m3u8",index / tsNeedMergeCount);
//        deleteFile(path);
//    }
    private void  deleteTS(M3U8Seg ts) {
        if (ts == null) {
            return;
        }
        String path = getTSPath(ts);
        deleteFile(path);
    }
    private String getTSPath(M3U8Seg ts) {
        String name = ts.getIndexName();
        if (!ts.getName().equals(ts.getUrl())) {
            name = ts.getName();
        }
        if (!name.endsWith(".ts")) {
            String suffixName = VideoDownloadUtils.getSuffixName(name);
            if (suffixName.length() == 0) {
                name = VideoDownloadUtils.SEGMENT_PREFIX + ts.getIndex() + ".ts";
            }else {
                name = name.replace(suffixName,".ts");
            }

        }
        return mSaveDir + File.separator + name;
    }
    private void  deleteFile(String path) {
        File tsFile = new File(path);
        if (tsFile.exists()) {
            if (tsFile.delete()) {
                LogUtils.i(TSMergeTAG,"delete file:" + path);
            }
        }
    }
    //判断是否连续的ts下载完成
    public int hasConsecutive(int index) {
        int result = index;
        for (int i = index ; (i < index + tsNeedMergeCount && i < mTotalTs) ; i ++) {
            M3U8Seg ts = finishTSMap.get(String.valueOf(i));

            if (ts == null || ts.getDownloadStatus() == 0 || ts.getMerge() != 0) {
                result = -1;
                if (ts == null) {
//                    LogUtils.e(TSMergeTAG,"ts failed mergeList index:" + i +" no this file");
                }

                break;
            }else {
//                LogUtils.e(TSMergeTAG,"ts can mergeList index:" + i+" ts index:"+ts.getIndex()+"ts path:"+getTSPath(ts));
            }
//            else {
//                String path = getTSPath(ts);
//                File file = new File(path);
//                if (!file.exists()) {
//                    return -1;
//                }
//            }
        }
//        LogUtils.i(TSMergeTAG,"check index :"+ index + "\n result is :" + result);
        return result;
    }
    private String createLocalM3U8FileForFMP4(File tsFile) throws IOException {
        synchronized (mFmp4FileLock) {
            File tempM3U8File = new File(mSaveDir, "transform.m3u8");
            if (tempM3U8File.exists()) {
                tempM3U8File.delete();
            }
            BufferedWriter bfw = new BufferedWriter(new FileWriter(tempM3U8File, false));
            bfw.write(M3U8Constants.PLAYLIST_HEADER + "\n");
            bfw.write(M3U8Constants.TAG_VERSION + ":" + mM3U8.getVersion() + "\n");
            bfw.write(M3U8Constants.TAG_MEDIA_SEQUENCE + ":" + 1 + "\n");

            bfw.write(M3U8Constants.TAG_TARGET_DURATION + ":" + mM3U8.getDuration() + "\n");
            M3U8Seg m3u8Ts = mTsList.get(0);
            if (m3u8Ts.hasInitSegment()) {
                String initSegmentInfo;
                String initSegmentFilePath = getMapHeaderPath(0);
                initSegmentInfo = "URI=\"" + initSegmentFilePath + "\"";

                bfw.write(M3U8Constants.TAG_INIT_SEGMENT + ":" + initSegmentInfo + "\n");
            }
            if (m3u8Ts.hasKey()) {
                if (m3u8Ts.getMethod() != null) {
                    String key = "METHOD=" + m3u8Ts.getMethod();
                    if (m3u8Ts.getKeyUri() != null) {
                        File keyFile = new File(mSaveDir, m3u8Ts.getLocalKeyUri());
                        if (!m3u8Ts.isMessyKey() && keyFile.exists()) {
                            key += ",URI=\"" + keyFile.getAbsolutePath() + "\"";
                        } else {
                            key += ",URI=\"" + m3u8Ts.getKeyUri() + "\"";
                        }
                    }
                    if (m3u8Ts.getKeyIV() != null) {
                        key += ",IV=" + m3u8Ts.getKeyIV();
                    }
                    bfw.write(M3U8Constants.TAG_KEY + ":" + key + "\n");
                }
            }
            if (m3u8Ts.hasDiscontinuity()) {
                bfw.write(M3U8Constants.TAG_DISCONTINUITY + "\n");
            }
            bfw.write(M3U8Constants.TAG_MEDIA_DURATION + ":" + mM3U8.getDuration() + ",\n");
            bfw.write(tsFile.getAbsolutePath());
            bfw.newLine();
//            }
            bfw.write(M3U8Constants.TAG_ENDLIST);
            bfw.flush();
            bfw.close();

            return tempM3U8File.getAbsolutePath();
        }
    }
    private String createLocalM3U8FileForDecrypt(File tsFile,int index) throws IOException {
        synchronized (mFmp4FileLock) {
            File tempM3U8File = new File(mSaveDir, decryptFilePre + index+".m3u8");
            if (tempM3U8File.exists()) {
                tempM3U8File.delete();
            }
            BufferedWriter bfw = new BufferedWriter(new FileWriter(tempM3U8File, false));
            bfw.write(M3U8Constants.PLAYLIST_HEADER + "\n");
            bfw.write(M3U8Constants.TAG_VERSION + ":" + mM3U8.getVersion() + "\n");
            bfw.write(M3U8Constants.TAG_MEDIA_SEQUENCE + ":" + 1 + "\n");

            bfw.write(M3U8Constants.TAG_TARGET_DURATION + ":" + mM3U8.getDuration() + "\n");
            M3U8Seg m3u8Ts = mTsList.get(0);
            if (m3u8Ts.hasInitSegment()) {
                String initSegmentInfo;
                String initSegmentFilePath = getMapHeaderPath(0);
                initSegmentInfo = "URI=\"" + initSegmentFilePath + "\"";

                bfw.write(M3U8Constants.TAG_INIT_SEGMENT + ":" + initSegmentInfo + "\n");
            }
            if (m3u8Ts.hasKey()) {
                if (m3u8Ts.getMethod() != null) {
                    String key = "METHOD=" + m3u8Ts.getMethod();
                    if (m3u8Ts.getKeyUri() != null) {
                        File keyFile = new File(mSaveDir, m3u8Ts.getLocalKeyUri());
                        if (!m3u8Ts.isMessyKey() && keyFile.exists()) {
                            key += ",URI=\"" + keyFile.getAbsolutePath() + "\"";
                        } else {
                            key += ",URI=\"" + m3u8Ts.getKeyUri() + "\"";
                        }
                    }
                    if (m3u8Ts.getKeyIV() != null) {
                        key += ",IV=" + m3u8Ts.getKeyIV();
                    }
                    bfw.write(M3U8Constants.TAG_KEY + ":" + key + "\n");
                }
            }
            if (m3u8Ts.hasDiscontinuity()) {
                bfw.write(M3U8Constants.TAG_DISCONTINUITY + "\n");
            }
            bfw.write(M3U8Constants.TAG_MEDIA_DURATION + ":" + mM3U8.getDuration() + ",\n");
            bfw.write(tsFile.getAbsolutePath());
            bfw.newLine();
//            }
            bfw.write(M3U8Constants.TAG_ENDLIST);
            bfw.flush();
            bfw.close();

            return tempM3U8File.getAbsolutePath();
        }
    }
}
